package org.daochong.fastdev;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.daochong.fastdev.funcs.ConditionFunction;
import org.daochong.lang.BeanUtil;
import org.daochong.lang.EntryString;
import org.daochong.lang.JdbcTemplateProvider;
import org.daochong.lang.Properties;
import org.daochong.lang.StringUtils;
import org.daochong.lang.uai.UniteAccessInterfaceFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class DefaultExecutor implements IExecutor, ApplicationContextAware {

	public static String DEFAULT_OPER_API = "api";

	public static String DEFAULT_OPER_JPA = "jpa";

	private IClassMappingFactory classMappingFactory;

	private IActionConfigFactory actionConfigFactory;

	private IFunctionFactory functionFactory;

	private JdbcTemplateProvider jdbcTemplateProvider;

	private UniteAccessInterfaceFactory uaiFactory;

	private ApplicationContext applicationContext;

	private List<IContextFilter> filters = null;

	public List<IContextFilter> getFilters() {
		if (filters == null) {
			filters = new ArrayList<IContextFilter>();
			filters.addAll(getDefaults(IContextFilter.class));
		}
		return filters;
	}

	public void setFilters(List<IContextFilter> filters) {
		this.filters = filters;
	}

	private <T> T getDefault(Class<T> clazz) {
		return this.applicationContext.getBean(clazz);
	}

	private <T> Collection<T> getDefaults(Class<T> clazz) {
		return this.applicationContext.getBeansOfType(clazz).values();
	}

	public UniteAccessInterfaceFactory getUaiFactory() {
		return uaiFactory != null ? uaiFactory : getDefault(UniteAccessInterfaceFactory.class);
	}

	public void setUaiFactory(UniteAccessInterfaceFactory uaiFactory) {
		this.uaiFactory = uaiFactory;
	}

	public JdbcTemplateProvider getJdbcTemplateProvider() {
		return jdbcTemplateProvider != null ? jdbcTemplateProvider : getDefault(JdbcTemplateProvider.class);
	}

	public void setJdbcTemplateProvider(JdbcTemplateProvider jdbcTemplateProvider) {
		this.jdbcTemplateProvider = jdbcTemplateProvider;
	}

	public IFunctionFactory getFunctionFactory() {
		return functionFactory != null ? functionFactory : getDefault(IFunctionFactory.class);
	}

	public void setFunctionFactory(IFunctionFactory functionFactory) {
		this.functionFactory = functionFactory;
	}

	private void before(Context context) {
		for (IContextFilter filter : this.getFilters()) {
			if (filter instanceof IPathContextFilter) {
				if (((IPathContextFilter) filter).needFilter(context)) {
					filter.before(context);
				}
			} else {
				filter.before(context);
			}
		}
	}

	private void after(Context context) {
		for (IContextFilter filter : this.getFilters()) {
			if (filter instanceof IPathContextFilter) {
				if (((IPathContextFilter) filter).needFilter(context)) {
					filter.after(context);
				}
			} else {
				filter.after(context);
			}
		}
	}

	private void error(Context context) {
		for (IContextFilter filter : this.getFilters()) {
			if (filter instanceof IPathContextFilter) {
				if (((IPathContextFilter) filter).needFilter(context)) {
					filter.error(context);
				}
			} else {
				filter.error(context);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private Map<String, Object> getMap(Context context, String prefix) {
		Object o = context.getAttribute(prefix);
		if (o == null) {
			Map<String, Object> map = new LinkedHashMap<String, Object>();
			context.setAttribute(prefix, map);
			return map;
		} else if (o instanceof Map) {
			return (Map<String, Object>) o;
		} else {
			return null;
		}
	}

	public Context exec(String prefix, String id, Map<String, Object> params, Object source) {
		return exec(prefix, id, params, null, source);
	}

	public Context exec(String prefix, String id, Map<String, Object> params, Map<String, Object> attrs,
			Object source) {
		if (StringUtils.isEmpty(id)) {
			throw new RuntimeException("[" + id + "] is null");
		}
		Context context = new Context();
		context.setSource(source);
		context.setOperType(DEFAULT_OPER_API);
		context.setOperData(prefix);
		context.setOperPath(id);
		context.addParameters(params);
		if (attrs != null) {
			context.addAttributes(attrs);
		}
		context.setJdbcTemplateProvider(getJdbcTemplateProvider());
		context.setClassMappingFactory(getClassMappingFactory());
		Properties p = this.getActionConfigFactory().getConfig(id);
		if (p == null)
			throw new RuntimeException("[" + id + "] is not config");
		getMap(context, DEFAULT_OPER_API).put("config", p);
		before(context);
		Properties config = p.clone();
		String str = config.getProperty("METHOD");
		if ((str != null) && (source instanceof HttpServletRequest)) {
			HttpServletRequest req = (HttpServletRequest) source;
			boolean find = false;
			for (String m : str.split(",")) {
				if (m.equalsIgnoreCase(req.getMethod())) {
					find = true;
					break;
				}
			}
			if (!find) {
				throw new RuntimeException("METHOD " + req.getMethod() + " not support");
			}
		}
		Properties errors = config.searchSubKeyRemoveProp(prefix + "error.");
		try {
			exec(config.searchSubKeyRemoveProp(prefix), context);
			after(context);
		} catch (Throwable e) {
			context.setSuccess(false);
			context.addException(e);
			try {
				exec(errors, context);
				error(context);
			} catch (SQLException e1) {
				context.addException(e1);
				throw new RuntimeException(e1);
			}
			if (e instanceof RuntimeException) {
				throw (RuntimeException) e;
			}
			throw new RuntimeException(e);
		}
		return context;
	}

	public void exec(Properties config, Context context) throws SQLException {
		context.setCurrentConfig(config);
		for (EntryString es : config.getEntrys()) {
			String key = es.getKey();
			String pk = BeanUtil.formatString(key, context);
			Function func = this.getFunctionFactory().getFunction(pk);
			if (func == null)
				continue;
			if (!(func instanceof ConditionFunction) && context.isCondition() && !context.getConditionCurrent()) {
				continue;
			}
			func.exec(pk, es.getValue(), context);
			if (context.isEnd()) {
				break;
			}
		}
	}

	public Context execJpa(String entityType, String method, Map<String, Object> params, Object source) {
		return execJpa(entityType, method, params, null, source);
	}

	public Context execJpa(String entityType, String method, Map<String, Object> params, Map<String, Object> attrs,
			Object source) {
		if (StringUtils.isEmpty(entityType)) {
			throw new RuntimeException("[" + entityType + "] is null");
		}
		return execJpa(this.getClassMappingFactory().getEntity(entityType), method, params, source);
	}

	public IClassMappingFactory getClassMappingFactory() {
		return classMappingFactory != null ? classMappingFactory : getDefault(IClassMappingFactory.class);
	}

	public void setClassMappingFactory(IClassMappingFactory classMappingFactory) {
		this.classMappingFactory = classMappingFactory;
	}

	public IActionConfigFactory getActionConfigFactory() {
		return actionConfigFactory != null ? actionConfigFactory : getDefault(IActionConfigFactory.class);
	}

	public void setActionConfigFactory(IActionConfigFactory actionConfigFactory) {
		this.actionConfigFactory = actionConfigFactory;
	}

	public Context execJpa(Class<?> clazz, String method, Map<String, Object> params, Object source) {
		return execJpa(clazz, method, params, null, source);
	}

	public Context execJpa(Class<?> clazz, String method, Map<String, Object> params, Map<String, Object> attrs,
			Object source) {
		String entityType = clazz.getSimpleName();
		Context context = new Context();
		context.setSource(source);
		context.addParameters(params);
		context.setOperType(DEFAULT_OPER_JPA);
		context.setOperPath(clazz.getName());
		context.setOperData(method);
		context.setJdbcTemplateProvider(getJdbcTemplateProvider());
		context.setClassMappingFactory(getClassMappingFactory());
		Properties p = this.getActionConfigFactory().getEntityConfig(entityType);
		if (p == null)
			p = new Properties();
		getMap(context, DEFAULT_OPER_JPA).put("config", p);
		before(context);
		Properties config = p.clone().searchSubKeyRemoveProp(method + ".");
		Properties befores = config.searchSubKeyRemoveProp("before.");
		Properties errors = config.searchSubKeyRemoveProp("error.");

		try {
			exec(befores, context);
			Object entity = BeanUtil.pushBean(context.getParameters(), clazz);
			String sqlId = config.getProperty(method);
			if (sqlId != null) {
				config.removeKey(method);
			}
			getMap(context, DEFAULT_OPER_JPA).put("entity", entity);
			Map<String, Object> extData = BeanUtil.getExtDatas(entity, context.getParameters());
			if ("GET".equalsIgnoreCase(method)) {
				if (sqlId == null) {
					context.add(entityType, context.getJpaOperations(config).load(entity));
				} else {
					context.add(entityType,
							context.getJpaOperations(config).loadBySql(clazz, sqlId, context.getParameters()));
				}
			} else if ("POST".equalsIgnoreCase(method)) {
				if (sqlId == null) {
					context.add(entityType, context.getJpaOperations(config).create(entity, extData));
				} else {
					context.add(entityType,
							context.getJpaOperations(config).createBySql(sqlId, context.getParameters()));
				}
			} else if ("PUT".equalsIgnoreCase(method)) {
				if (sqlId == null) {
					context.add(entityType, context.getJpaOperations(config).updateWithoutNull(entity, extData));
				} else {
					context.add(entityType,
							context.getJpaOperations(config).updateBySql(sqlId, context.getParameters()));
				}
			} else if ("DELETE".equalsIgnoreCase(method)) {
				if (sqlId == null) {
					context.add(entityType, context.getJpaOperations(config).delete(entity));
				} else {
					context.add(entityType,
							context.getJpaOperations(config).deleteBySql(sqlId, context.getParameters()));
				}
			}
			exec(config.searchSubKeyRemoveProp(method), context);
			after(context);
		} catch (Throwable e) {
			context.setSuccess(false);
			context.addException(e);
			try {
				exec(errors, context);
				error(context);
			} catch (SQLException e1) {
				context.addException(e1);
				throw new RuntimeException(e1);
			}
			throw new RuntimeException(e);
		}
		return context;
	}

	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	public Object getBean(String name) {
		return this.applicationContext.getBean(name);
	}

	public <T> T getBean(Class<T> clazz) {
		return this.getBean(clazz);
	}

}
